using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using ZoneFiveSoftware.Common.Data;
using ZoneFiveSoftware.Common.Data.Fitness;
using FilteredStatistics.Common;
using FilteredStatistics.Common.Controller;
using FilteredStatistics.Controller;

namespace FilteredStatistics.Data
{
    class Filter : IPluginSerializable
    {
        // Type used for backwards compatibility with version < 3
        private enum FilterType
        {
            Cadence = 0,
            Climb,
            HeartRate,
            Speed,
            Power,
            Splits,
            Gears,
            Template,
            PostProcess,
            FilterTypeCount
        }

        public Filter()
        {
        }

#region IPluginSerializable members

        public override void Serialize(Stream stream)
        {
            SerializableString zoneRefID = new SerializableString(CriteriaFacade.ReferenceId.ToString());
            zoneRefID.Serialize(stream);

            // Serialize the criteria version number
            SerializableUInt16Range criteriaVersionNumber = new SerializableUInt16Range(CriteriaFacade.DataVersion);
            criteriaVersionNumber.Serialize(stream);

            // Serialize in a temp buffer so we can get the size
            MemoryStream tempStream = new MemoryStream();
            CriteriaFacade.SerializeCriteria(tempStream);

            // Serialize criteria serialization size to be able to skip if an error occurs
            SerializableInt64Range criteriaSize = new SerializableInt64Range(tempStream.Length);
            criteriaSize.Serialize(stream);

            // Serialize the buffer
            Debug.Assert(((int)tempStream.Length) == tempStream.Length); // Type conversion fuck up... make sure it fits
            stream.Write(tempStream.GetBuffer(), 0, (int)tempStream.Length);

            // Serialize the selected zones
            SerializableInt32Range zonesCount = new SerializableInt32Range(Zones.Count);
            zonesCount.Serialize(stream);

            foreach (INamedZone zone in Zones)
            {
                // Serialize the index
                SerializableInt32Range index = new SerializableInt32Range(CriteriaFacade.ConcreteNamedZones.IndexOf(zone));

                index.Serialize(stream);
            }
        }

#endregion

#region Deserialization methods

        public void Deserialize_V1(Stream stream, DataVersion version)
        {
            FilterCriteriaFacade criteriaToDeserialize = null;
            SerializableInt32Range type = new SerializableInt32Range(0);

            type.Deserialize(stream, version);
            FilterType filterType = (FilterType)(Int32)type;

            // Get the criteria template to deserialize
            switch (filterType)
            {
                case FilterType.Cadence:
                case FilterType.Climb:
                case FilterType.HeartRate:
                case FilterType.Power:
                case FilterType.Speed:
                {
                    SerializableString zoneRefID = new SerializableString();

                    zoneRefID.Deserialize(stream, version);

                    criteriaToDeserialize = FilterCriteriaController.Instance.GetCriteria(new Guid(zoneRefID)) as FilterCriteriaFacade;
                    break;
                }
                case FilterType.Splits:
                {
                    criteriaToDeserialize = FilterCriteriaController.Instance.GetCriteria(GUIDs.TemplateSplitsCriteriaGuid) as FilterCriteriaFacade;
                    break;
                }
                case FilterType.Gears:
                {
                    criteriaToDeserialize = FilterCriteriaController.Instance.GetCriteria(GUIDs.GearsCriteriaGuid) as FilterCriteriaFacade;

                    if (criteriaToDeserialize == null)
                    {
                        // Skip deserialization data, gear plugin not installed
                        SerializableString equipmentID = new SerializableString();
                        equipmentID.Deserialize(stream, version);
                    }
                    break;
                }
                case FilterType.Template:
                {
                    criteriaToDeserialize = FilterCriteriaController.Instance.GetCriteria(GUIDs.TemplateCriteriaGuid) as FilterCriteriaFacade;
                    break;
                }
                case FilterType.PostProcess:
                {
                    criteriaToDeserialize = FilterCriteriaController.Instance.GetCriteria(GUIDs.PostProcessCriteriaGuid) as FilterCriteriaFacade;
                    break;
                }
                default:
                {
                    Debug.Assert(false);
                    break;
                }
            }

            // Deserialize from the template
            if (criteriaToDeserialize != null)
            {
                Criteria = criteriaToDeserialize.DeserializeCriteriaFacade(stream, version.VersionNumber);
                
                if (CriteriaFacade is PostProcessFilterCriteria)
                {
                    // Deselect the auto-selection...
                    ClearSelectedZonesInternal();
                }
            }
            else
            {
                Criteria = null;
            }

            SerializableInt32Range zonesCount = new SerializableInt32Range(0);

            zonesCount.Deserialize(stream, version);

            for (int i = 0; i < zonesCount; ++i)
            {
                SerializableInt32Range zoneIndex = new SerializableInt32Range(0);

                zoneIndex.Deserialize(stream, version);

                if (CriteriaFacade != null &&
                    zoneIndex < CriteriaFacade.ConcreteNamedZones.Count)
                {
                    AddSelectedZone(CriteriaFacade.ConcreteNamedZones[zoneIndex]);
                }
            }
        }


        public void Deserialize_V3(Stream stream, DataVersion version)
        {
            // Criteria deserialization
            SerializableString zoneRefID = new SerializableString();

            zoneRefID.Deserialize(stream, version);

            FilterCriteriaFacade tempCriteria = FilterCriteriaController.Instance.GetCriteria(new Guid(zoneRefID)) as FilterCriteriaFacade;

            if (tempCriteria != null)
            {
                long streamPosition;
                SerializableUInt16Range criteriaVersionNumber = new SerializableUInt16Range(0);
                SerializableInt64Range criteriaSize = new SerializableInt64Range(0);

                criteriaVersionNumber.Deserialize(stream, version);
                streamPosition = stream.Position;
                criteriaSize.Deserialize(stream, version);

                try
                {
                    Criteria = tempCriteria.DeserializeCriteriaFacade(stream, criteriaVersionNumber);
                }
                catch
                {
                    Criteria = null;
                }

                if (CriteriaFacade == null)
                {
                    // Skip criteria section to allow the deserialization of the remaining
                    //  filters
                    stream.Seek(streamPosition + criteriaSize, SeekOrigin.Begin);
                }
                else if (CriteriaFacade is PostProcessFilterCriteria)
                {
                    // Deselect the auto-selection...
                    ClearSelectedZonesInternal();
                }
            }
            else
            {
                SerializableUInt16Range criteriaVersionNumber = new SerializableUInt16Range(0);
                SerializableInt64Range criteriaSize = new SerializableInt64Range(0);

                criteriaVersionNumber.Deserialize(stream, version);
                criteriaSize.Deserialize(stream, version);

                // Skip section, the criteria wasn't registered
                stream.Seek(criteriaSize, SeekOrigin.Current);
            }

            SerializableInt32Range zonesCount = new SerializableInt32Range(0);

            zonesCount.Deserialize(stream, version);

            for (int i = 0; i < zonesCount; ++i)
            {
                SerializableInt32Range zoneIndex = new SerializableInt32Range(0);

                zoneIndex.Deserialize(stream, version);

                // Check for a valid criteria because we might have skipped it above
                if (CriteriaFacade != null &&
                    zoneIndex < CriteriaFacade.ConcreteNamedZones.Count)
                {
                    AddSelectedZone(CriteriaFacade.ConcreteNamedZones[zoneIndex]);
                }
            }
        }

#endregion

        protected void TriggerFilterChangedEvent(string propertyName)
        {
            if (FilterChanged != null)
            {
                FilterChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public Filter Clone()
        {
            Filter copy = new Filter();

            copy.Criteria = CriteriaFacade.GetTemplateCompatibleCriteriaFacade();
            copy.ClearSelectedZones();

            Debug.Assert(copy.CriteriaFacade.ConcreteNamedZones.Count <= CriteriaFacade.ConcreteNamedZones.Count);

            for (int i = 0; i < copy.CriteriaFacade.ConcreteNamedZones.Count; ++i)
            {
                if (IsZoneSelected(CriteriaFacade.ConcreteNamedZones[i]))
                {
                    copy.AddSelectedZone(copy.CriteriaFacade.ConcreteNamedZones[i]);
                }
            }

            return copy;
        }

        public int GetSelectedZoneCount()
        {
            return m_Zones.Count;
        }

        public void SetSelectedZones(List<INamedZone> zones)
        {
            bool isNewListDifferent = CheckIfListAreDifferent(Zones, zones);

            if (isNewListDifferent)
            {
                ClearSelectedZonesInternal();

                foreach (INamedZone zone in zones)
                {
                    zone.ValidTimesChanged += new PropertyChangedEventHandler(OnSelectionValidTimesChanged);
                }
                m_Zones.AddRange(zones);

                TriggerFilterChangedEvent("Zones");
            }
        }

        public void AddSelectedZone(INamedZone zone)
        {
            Debug.Assert(m_Zones.IndexOf(zone) == -1);

            zone.ValidTimesChanged += new PropertyChangedEventHandler(OnSelectionValidTimesChanged);
            m_Zones.Add(zone);

            TriggerFilterChangedEvent("Zones");
        }

        public void RemoveSelectedZone(INamedZone zone)
        {
            Debug.Assert(m_Zones.Contains(zone));

            zone.ValidTimesChanged -= new PropertyChangedEventHandler(OnSelectionValidTimesChanged);
            m_Zones.Remove(zone);

            TriggerFilterChangedEvent("Zones");
        }

        public void ClearSelectedZones()
        {
            if (m_Zones.Count > 0)
            {
                ClearSelectedZonesInternal();

                TriggerFilterChangedEvent("Zones");
            }
        }

        public void ClearSelectedZonesInternal()
        {
            if (m_Zones.Count > 0)
            {
                foreach (INamedZone zone in m_Zones)
                {
                    zone.ValidTimesChanged -= new PropertyChangedEventHandler(OnSelectionValidTimesChanged);
                }

                m_Zones.Clear();
            }
        }

        public bool IsZoneSelected(INamedZone zone)
        {
            return m_Zones.Contains(zone);
        }

        private void OnSelectionValidTimesChanged(object sender, PropertyChangedEventArgs e)
        {
            int changedZoneIndex = m_Zones.IndexOf(sender as INamedZone);

            Debug.Assert(changedZoneIndex != -1);

            TriggerFilterChangedEvent("ValidTimes");
        }

        void OnCriteriaNamedZonesListChanged(object sender, PropertyChangedEventArgs e)
        {
            // Go through the list of selected zones and reselect the ones that
            //  are still valid
            List<INamedZone> newSelection = new List<INamedZone>();

            foreach (INamedZone selectedZone in Zones)
            {
                if (CriteriaFacade.NamedZones.Contains(selectedZone))
                {
                    newSelection.Add(selectedZone);
                }
            }

            SetSelectedZones(newSelection);

            TriggerFilterChangedEvent("NamedZones");
        }

        private bool CheckIfListAreDifferent(IList<INamedZone> list1, IList<INamedZone> list2)
        {
            if (list1.Count != list2.Count)
            {
                return true;
            }
            else
            {
                // Check item per item for a mismatch
                for (int i = 0; i < list2.Count; ++i)
                {
                    if (!list1.Contains(list2[i]))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        public bool IsValid
        {
            get
            {
                return CriteriaFacade != null &&
                       Zones.Count > 0;
            }
        }

        public FilterCriteriaFacade CriteriaFacade
        {
            get
            {
                Debug.Assert(m_FilterCriteria == null || m_FilterCriteria is FilterCriteriaFacade);

                return m_FilterCriteria as FilterCriteriaFacade;
            }
        }

        public IFilterCriteria Criteria
        {
            set
            {
                if (value != m_FilterCriteria)
                {
                    if (m_FilterCriteria != null)
                    {
                        m_FilterCriteria.NamedZonedListChanged -= new PropertyChangedEventHandler(OnCriteriaNamedZonesListChanged);
                    }

                    m_FilterCriteria = value;

                    if (m_FilterCriteria != null)
                    {
                        m_FilterCriteria.NamedZonedListChanged += new PropertyChangedEventHandler(OnCriteriaNamedZonesListChanged);
                    }

                    if (m_FilterCriteria is PostProcessFilterCriteria)
                    {
                        // Always select all for post process by default
                        SetSelectedZones(CriteriaFacade.ConcreteNamedZones);
                    }
                    else
                    {
                        ClearSelectedZonesInternal();
                    }

                    TriggerFilterChangedEvent("Criteria");
                }
            }
        }

        public List<INamedZone> Zones
        {
            get { return m_Zones; }
        }

        public delegate void FilterChangedEventHandler(Filter filter, PropertyChangedEventArgs changedProperty);
        public event FilterChangedEventHandler FilterChanged;

        private IFilterCriteria m_FilterCriteria = null;
        private List<INamedZone> m_Zones = new List<INamedZone>();
    }
}
